// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.http.logic.dto;

import java.util.ArrayList;
import java.util.List;
import java.util.Observer;

import com.eva.epc.common.util.CommonUtils;

/**
 * 用户个人信息封装类。
 * <p>
 * 本类中的大部分字段意义以数据字典中“用户信息/MISSU_USERS”表保持一致。
 * <p>
 * <b>特别说明</b>：<font color="red">由于历史原因，本类的命名不够规范，以后的版本中或将重构为“UserInfo”类名！</font>
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 1.0
 */
public class RosterElementEntity implements java.io.Serializable
{
	/** RainbowChat中约定uid为"-1"表示默认的无效用户uid. */
	public final static String DEFAULT_INVALID_UID_VALUE = "-1";
	
	/** 用户在线状态常量：在线 */
	public final static int LIVE_STATUS_ONLINE = 1;
	/** 用户在线状态常量：离线 */
	public final static int LIVE_STATUS_OFFLINE = 0;
	
	/** 用户性别常量：男 */
	public final static String SEX_MAN = "1";
	/** 用户性别常量：女 */
	public final static String SEX_WOMAN = "0";
	
	/*
	 * ===========【说明】_ex1至_ex10为保留字段，以便未来系统更新时备用，目的在于因使用的是java序列化对象，因而
	 * 理论上来讲如重大变更，KLink正式版发布后，本类将不应改变（否则客户端将会序列化对象不一致的错误），
	 * 如有新字段需求则利用目前的保留字段来实现之！ */
	
	/** 
	 * 本字段当前仅用作服务端转发加好友请求（给B）时存放“验证说明文本”使用（包括离线时的好友请求）.
	 * 
	 * @since 2.2
	 */
	// 注意：_如“ex1”这种首字母带下划线的形式，fastjson转json时会忽略此字段！
	protected String ex1;
	
	/** 
	 * 本字段当前仅用作服务端转发加好友请求（给B）时存放“加好友请求发起时间戳”使用（包括离线时的好友请求）.
	 * 
	 * @since 4.2
	 */
	protected String ex10;
	
	/** 保留字段11：留作协议升级时备用，生产环境下备用字段将能在协议升级时起到兼容旧版本的作用。 */
	protected String ex11;
	/** 保留字段12：留作协议升级时备用，生产环境下备用字段将能在协议升级时起到兼容旧版本的作用。 */
	protected String ex12;
	/** 保留字段13：留作协议升级时备用，生产环境下备用字段将能在协议升级时起到兼容旧版本的作用。 */
	protected String ex13;
	/** 保留字段14：留作协议升级时备用，生产环境下备用字段将能在协议升级时起到兼容旧版本的作用。 */
	protected String ex14;
	/** 保留字段15：留作协议升级时备用，生产环境下备用字段将能在协议升级时起到兼容旧版本的作用。 */
	protected String ex15;
	
	/** 
	 * 本字段用于控制Android客户端的版本。作用是当出现某致命bug或重要功能时，
	 * 可以强制要求用户更新，否则将无法登陆进APP。
	 * <p>
	 * <b>用法是：</b><br>
	 * * 当本字段为空时，表示不开启强制更新，否由开启强制更新；<br>
	 * * 当客户端版本小于本次强制版本号时，客户端将会被强制要求更新，更新说明即是 {@link #ex14}所指明的内容。
	 * <p>
	 * <b>补充说明：</b><br>
	 * 本字段值为null或整数字符串，版本号对应于Android端的AndroidManifest.xml文件中定义的versionCode。
	 */
	protected String mustVersion4A;
	/** 
	 * 本字段配合 {@link #mustVersion4A} 完成强制Android端版本更新的控制。
	 * <p>
	 * 当开启强制客户端更新时，客户端会将此字段内容显示在UI上，提升用户体验。 
	 */
	protected String mustVersionDesc4A;
	
	/** 
	 * 存放于文件服务器的用户头像文件名。
	 * <p>
	 * 用户客户端缓存时使用，因为用户头像读取的HTTP服务URL并不是静态地址，而是复杂的动态查询URL，
	 * 在缓存文件时就无法通过URL通取到文件名称，所以本参数的作用就是缓存时用于文件名称哦）。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 */
	protected String userAvatarFileName;
	
	/** 
	 * 存放用户心情（60个字符哦）。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 */
	protected String whatsUp;
	
	/** 
	 * 存放该用户允许的最多好友个数。
	 * <p>
	 * 说明：本字段存放的是String表示的整数。详见数据字典中
	 * “用户信息/MISSU_USERS”表的设计说明。
	 * 
	 * @since 2.0
	 */
	protected String maxFriend;
	
	/** 
	 * 存放该用户的个人其它说明文字。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 * 
	 * @since 2.5
	 */
	protected String userDesc;
	
	/** 
	 * 存放该用户的用户类型（无符号整数）。
	 * <p>
	 * 本字段目前主要用于区分普通IM用户和公司运营后台用户。具体意义详见
	 * 数据字典中表“用户信息/MISSU_USERS”中user_type字段的定义。
	 * 
	 * @since 2.5.1
	 */
	protected String userType;
	
	/**
	 * 本字段为RainbowChat系统中的用户唯id，是全系统的唯一标识。
	 * <p>
	 * 无论用户登陆时使用什么（可能是邮件地址、手机号、或者直接像qq一样用唯一id号），
	 * 此id在任何情况下都是一个用户的唯一合法身份标识。
	 * <p>
	 * 为配合RainbowChat升级MobileIMSDK v3时而增加，本对象如果是由登陆接口返回，则将
	 * 随后与 {@link #token} 一起在返回到客户端后，分别作为loginUserId和loginToken
	 * 来登陆连接IM服务器之用。
	 */
	protected String user_uid;
	/** 
	 * 用户的邮箱地址。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。 
	 */
	protected String user_mail;
	
	/** 
	 * 用户的昵称。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 */
	protected String nickname;
	/**
	 * 用户的性别。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 */
	protected String user_sex;
	/** 
	 * 用户的注册时间。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。 
	 */
	protected String register_time;
	
	/** 
	 * 实际存放的是数据库表missu_users中的latest_login_time2字段。<br>
	 * 说明：本字段存放的是String表示的long整数. 
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 * 
	 * @since 2.0
	 */
	protected String latest_login_time;
	
	/** 
	 * 用户最近登陆ip。
	 * <p>
	 * 详见数据字典中“用户信息/MISSU_USERS”表的设计说明。
	 * 
	 * @since 4.5
	 */
	protected String latest_login_ip;
	
	/** 用户的在线状态：0 表示不在线，1 表示在线 */
	protected int liveStatus = LIVE_STATUS_OFFLINE;
	
	/**
	 * <b>用途1：用于IM的连接认证> </b>
	 * <p>
	 * 为了接下来客户端对接IM即时通讯框架而生成的token，主要用于安全策略，具体生成和使用方法由逻辑层定义。
	 * 本字段仅在从登陆接口返回时有意义，其它情况下无意义。
	 * <br>
	 * 截止20170216日止，本字段暂作保留字段，未来在架构优化和升级和，在服务端性能能得到保证的前提下再启
	 * 用此token的验证不迟。
	 * <p>
	 * 本字段为配合RainbowChat升级MobileIMSDK v3时而增加，将与 {@link #user_uid} 一起在返回到客户端
	 * 后，分别作为loginUserId和loginToken来登陆连接IM服务器之用。<br>
	 * TODO 为了便于解偶和对接IM实时框架的理解，未来可以考虑将http的登际认证接口独立为SSO单点登陆接口，
	 * 而与SSO接口配合使用会让MobileIMSDK v3的理解更为直观和简单。
	 * <p>
	 * <b>用途2：用于HTTP REST接口调用时的合法身份标识> </b>
	 * <p>
	 * 基于HTTP REST接口调用的安全考虑，此token建议每次调用rest接口时带上，以便rest服务端检查此次rest
	 * 表求的合法性。
	 * 
	 * @since 4.0
	 */
	protected String token = null;
	
	/** 
	 * 好友备注。
	 * <p>
	 * <b>注：</b>本字段对应于数据库表“missu_roster”中的“remark”字段（详见数据库字典），
	 * 当前仅用于客户端查询当前仅用于客户端查询好友列表数据时使用，其它情况下本字段无意义！
	 * @since 7.3
	 */
	protected String friendRemark;
	/** 
	 * 好友手机号。
	 * <p>
	 * <b>注：</b>本字段对应于数据库表“missu_roster”中的“mobile_num”字段（详见数据库字典），
	 * 当前仅用于客户端查询当前仅用于客户端查询好友列表数据时使用，其它情况下本字段无意义！
	 * @since 7.3
	 */
	protected String friendMobileNum;
	/** 
	 * 好友更多描述。
	 * <p>
	 * <b>注：</b>本字段对应于数据库表“missu_roster”中的“more_desc”字段（详见数据库字典），
	 * 当前仅用于客户端查询当前仅用于客户端查询好友列表数据时使用，其它情况下本字段无意义！
	 * @since 7.3
	 */
	protected String friendMoreDesc;
	/** 
	 * 好友相关图片、名片（存放的是文件名，详见数据库字典中的字段说明） 。
	 * <p>
	 * <b>注：</b>本字段对应于数据库表“missu_roster”中的“pic_file_name”字段（详见数据库字典），
	 * 当前仅用于客户端查询当前仅用于客户端查询好友列表数据时使用，其它情况下本字段无意义！
	 * @since 7.3
	 */
	protected String friendPicFileName;

	/**
	 * 本对象不需要序列化（因为仅用于客户端使用，与服务端无关）：用于保存好友的上下线通知观察者哦（全局只有一个这样的观察者）.
	 * <b>说明：</b>本观察者被通知时，update方法的data参数收到的将是一个String[]数组：元素0是昵称、元素1是上下线状态、元素2是uid.
	 * <b>补充：</b>之所以没放在客户端网络事件里，原因是有些时间设置上下线状态但又想通知观察者时，并非由网络事件导致，所以放在此处是
	 * @see #LIVE_STATUS_ONLINE
	 * @see #LIVE_STATUS_OFFLINE
	 */
	protected transient static List<Observer> liveStatusChangeObss = new ArrayList<>();

	public String getUser_uid()
	{
		return user_uid;
	}

	public RosterElementEntity setUser_uid(String user_uid)
	{
		this.user_uid = user_uid;
		return this;
	}

	public String getUser_mail()
	{
		return user_mail;
	}

	public RosterElementEntity setUser_mail(String user_mail)
	{
		this.user_mail = user_mail;
		return this;
	}

	public String getNickname()
	{
		return nickname;
	}
	
	/**
	 * 获取好友的备注昵称，当设置了好友备注时则返回的是备注，否则返回的是原昵称。
	 * 
	 * @return 返回好友备注优先的昵称
	 * @see #friendRemark
	 * @see #nickname
	 */
	public String getNickNameWithRemark() {
		if(CommonUtils.isStringEmpty(this.friendRemark, true))
			return this.nickname;
		return this.friendRemark;
	}

	public RosterElementEntity setNickname(String nickname)
	{
		this.nickname = nickname;
		return this;
	}

	public String getUser_sex()
	{
		return user_sex;
	}

	public RosterElementEntity setUser_sex(String user_sex)
	{
		this.user_sex = user_sex;
		return this;
	}
	
	public String getRegister_time()
	{
		return register_time;
	}

	public void setRegister_time(String register_time)
	{
		this.register_time = register_time;
	}

	public String getLatest_login_time()
	{
		return latest_login_time;
	}

	public void setLatest_login_time(String latest_login_time)
	{
		this.latest_login_time = latest_login_time;
	}

	public int getLiveStatus()
	{
		return liveStatus;
	}

	public RosterElementEntity setLiveStatus(int liveStatus)
	{
		this.liveStatus = liveStatus;

		// 并把上下线状态改变通知给观察者
		if(RosterElementEntity.getLiveStatusChangeObss().size() > 0) {
			for (Observer o : RosterElementEntity.getLiveStatusChangeObss()) {
				o.update(null, new String[]{this.nickname, "" + liveStatus, user_uid});
			}
		}
		
		return this;
	}
	
	public String getToken()
	{
		return token;
	}
	public void setToken(String tokenforIM)
	{
		this.token = tokenforIM;
	}

	/**
	 * 是否在线.
	 * 
	 * @return
	 */
	public boolean isOnline()
	{
		return this.liveStatus == LIVE_STATUS_ONLINE;
	}
	
	/**
	 * 用户上线设定方便方法.<br>
	 * 通过此方法可同时实现设置用户在线状态.
	 *
	 * @see #setLiveStatus(int)
	 */
	public void online()
	{		
		this.setLiveStatus(LIVE_STATUS_ONLINE);
	}
	/**
	 * 用户下线设定方便方法.<br>
	 * 通过此方法可同时实现设置用户在线状态.
	 *
	 * @see #setLiveStatus(int)
	 */
	public void offline()
	{
		this.setLiveStatus(LIVE_STATUS_OFFLINE);
	}
	
	/**
	 * 该用户是否男性.
	 * 
	 * @return true表示男性，否则表示女性
	 */
	public boolean isMan()
	{
		return SEX_MAN.equals(this.user_sex);
	}

	@Override
	public String toString()
	{
		return this.nickname;
	}
	
	//----------------------------------------------------------------------------------------- 保留字段 S
	public String getEx1()
	{
		return ex1;
	}
	public void setEx1(String ex1)
	{
		this.ex1 = ex1;
	}

	public String getMustVersion4A()
	{
		return mustVersion4A;
	}
	public void setMustVersion4A(String mustVersion4A)
	{
		this.mustVersion4A = mustVersion4A;
	}
	
	public String getMustVersionDesc4A()
	{
		return mustVersionDesc4A;
	}
	public void setMustVersionDesc4A(String mustVersionDesc4A)
	{
		this.mustVersionDesc4A = mustVersionDesc4A;
	}
	
	public String getUserAvatarFileName()
	{
		return userAvatarFileName;
	}
	public void setUserAvatarFileName(String userAvatarFileName)
	{
		this.userAvatarFileName = userAvatarFileName;
	}
	
	public String getWhatsUp()
	{
		return whatsUp;
	}
	public void setWhatsUp(String whatsUp)
	{
		this.whatsUp = whatsUp;
	}
	
	public String getMaxFriend()
	{
		return maxFriend;
	}
	public void setMaxFriend(String maxFriend)
	{
		this.maxFriend = maxFriend;
	}
	
	public String getUserDesc()
	{
		return userDesc;
	}
	public void setUserDesc(String userDesc)
	{
		this.userDesc = userDesc;
	}
	
	public String getUserType()
	{
		return userType;
	}
	public void setUserType(String userType)
	{
		this.userType = userType;
	}

	public String getLatest_login_ip()
	{
		return latest_login_ip;
	}

	public void setLatest_login_ip(String latest_login_ip)
	{
		this.latest_login_ip = latest_login_ip;
	}

	public String getEx10()
	{
		return ex10;
	}
	public void setEx10(String ex10)
	{
		this.ex10 = ex10;
	}

	public Object getEx11()
	{
		return ex11;
	}
	public void setEx11(String ex11)
	{
		this.ex11 = ex11;
	}

	public String getEx12()
	{
		return ex12;
	}
	public void setEx12(String ex12)
	{
		this.ex12 = ex12;
	}

	public String getEx13()
	{
		return ex13;
	}
	public void setEx13(String ex13)
	{
		this.ex13 = ex13;
	}

	public String getEx14()
	{
		return ex14;
	}
	public void setEx14(String ex14)
	{
		this.ex14 = ex14;
	}

	public String getEx15()
	{
		return ex15;
	}
	public void setEx15(String ex15)
	{
		this.ex15 = ex15;
	}
	
	public String getFriendRemark() {
		return friendRemark;
	}

	public void setFriendRemark(String friendRemark) {
		this.friendRemark = friendRemark;
	}

	public String getFriendMobileNum() {
		return friendMobileNum;
	}

	public void setFriendMobileNum(String friendMobileNum) {
		this.friendMobileNum = friendMobileNum;
	}

	public String getFriendMoreDesc() {
		return friendMoreDesc;
	}

	public void setFriendMoreDesc(String friendMoreDesc) {
		this.friendMoreDesc = friendMoreDesc;
	}

	public String getFriendPicFileName() {
		return friendPicFileName;
	}

	public void setFriendPicFileName(String friendPicFileName) {
		this.friendPicFileName = friendPicFileName;
	}

	protected RosterElementEntity doClone(RosterElementEntity newObj)
	{
		if(newObj != null) {
			newObj.setEx1(this.ex1);
			newObj.setEx10(this.ex10);
			newObj.setEx11(this.ex11);
			newObj.setEx12(this.ex12);
			newObj.setEx13(this.ex13);
			newObj.setEx14(this.ex14);
			newObj.setEx15(this.ex15);
			newObj.setMustVersion4A(this.mustVersion4A);
			newObj.setMustVersionDesc4A(this.mustVersionDesc4A);
			newObj.setUserAvatarFileName(this.userAvatarFileName);
			newObj.setWhatsUp(this.whatsUp);
			newObj.setMaxFriend(this.maxFriend);
			newObj.setUserDesc(this.userDesc);
			newObj.setUserType(this.userType);
			newObj.setUser_uid(this.user_uid);
			newObj.setUser_mail(this.user_mail);
			newObj.setNickname(this.nickname);
			newObj.setUser_sex(this.user_sex);
			newObj.setRegister_time(this.register_time);
			newObj.setLatest_login_time(this.latest_login_time);
			newObj.setLiveStatus(this.liveStatus);
			newObj.setToken(this.token);
			newObj.setFriendRemark(this.friendRemark);
			newObj.setFriendMobileNum(this.friendMobileNum);
			newObj.setFriendMoreDesc(this.friendMoreDesc);
			newObj.setFriendPicFileName(this.friendPicFileName);
		}
		return newObj;
	}
	
	/**
	 * RosterElementEntity 深度对象克隆方法实现。
	 * 
	 * @return 克隆完成后的新对象
	 * @since 4.0, 20170225
	 */
	@Override
	public Object clone()
	{
		RosterElementEntity cloneRee = new RosterElementEntity();
		return doClone(cloneRee);
	}
	
	
	//----------------------------------------------------------------------------------------- 保留字段 E
	
	public static void addLiveStatusChangeObs(Observer liveStatusChangeObs)
	{
		if(liveStatusChangeObs != null)
			RosterElementEntity.liveStatusChangeObss.add(liveStatusChangeObs);
	}

	public static void removeLiveStatusChangeObs(Observer liveStatusChangeObs)
	{
		if(liveStatusChangeObs != null)
			RosterElementEntity.liveStatusChangeObss.remove(liveStatusChangeObs);
	}

	public static List<Observer> getLiveStatusChangeObss()
	{
		return liveStatusChangeObss;
	}
}
